<?php

namespace App\Kuafu\Pay\Model;

use App\Kuafu\Base\Model\BaseModel;
use App\Kuafu\Base\Model\Traits\KfPayStreamSplit;
use App\Kuafu\Base\Model\Traits\KfSplitRelation;
use App\Kuafu\Base\Model\Traits\KfWaiMaiSplit;
use App\Kuafu\Common\Service\SeparateService;
use App\Kuafu\CustomPayment\Model\CustomPaymentModel;
use App\Kuafu\Order\Enum\CodeEnum;
use App\Kuafu\Pay\Enum\AliPayEnum;
use App\Kuafu\Pay\Enum\CashPayEnum;
use App\Kuafu\Pay\Enum\CustomPayEnum;
use App\Kuafu\Pay\Enum\FuYouPayEnum;
use App\Kuafu\Pay\Enum\MyBankCashierPayEnum;
use App\Kuafu\Pay\Enum\MyBankPayEnum;
use App\Kuafu\Pay\Enum\PayCodeEnum;
use App\Kuafu\Pay\Enum\WxPayEnum;
use App\Kuafu\Pay\Exceptions\PayException;
use App\Kuafu\Pay\Model\Clickhouse\PayStreamCHModel;
use App\Kuafu\Pay\Service\PayConfigService;
use App\Kuafu\Shop\Model\Shop;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class PayStreamModel extends BaseModel
{
    use KfPayStreamSplit;
    use KfSplitRelation;

    protected $connection   = 'mysql';
    protected $table        = 'pay_stream';
    protected $primaryKey   = 'payNo';
    public    $incrementing = false;
    protected $keyType      = 'string';

    protected $fillable = [
        'payNo',
        'shopId',
        'orderId',
        'thirdPayNo',
        'payStatus',
        'payPrice',
        'paySuccessTime',
        'payChannel',
        'payChannelChild',
        'payAction',
        'fee',
        'feeRate',
        'sysType',
        'callbackUrl',
        'subject',
        'isPaidIn',
        'createdAt',
        'updatedAt',
        'company',
        'companyAccount',
        'bizId',
        'createdByUid',
        'extra',
    ];

    protected $hidden = [
        'updatedAt',
        'isDeleted'
    ];

    // 支付直连渠道
    const PAY_CHANNEL_NULL               = 0; // 默认
    const PAY_CHANNEL_ALI                = 1; // 支付宝
    const PAY_CHANNEL_WX                 = 2; // 微信
    const PAY_CHANNEL_CASH               = 3; // 现金
    const PAY_CHANNEL_FY                 = 4; // 富友
    const PAY_CHANNEL_MYBANK_CONFIG      = 5; // 网商分账配置
    const PAY_CHANNEL_MYBANK_PAY         = 6; // 网商支付
    const PAY_CHANNEL_POST_SALE          = 7; // 订货用-售后支付
    const PAY_CHANNEL_UNION_PAY          = 8; // 银联,云闪付
    const PAY_CHANNEL_MYBANK_CASHIER_PAY = 9; // 网商小程序支付 3.2.2收银台支付--收方单边入驻

    // channel >= 100 都为自定义支付
    const PAY_CHANNEL_CUSTOM_PAYMENT = 100;


    const PAY_CHANNEL_MEITUAN = 10; // 美团（ShopPayment记录配置）

    const PAY_CHANNEL_TEXT = [
        self::PAY_CHANNEL_ALI                => '支付宝',
        self::PAY_CHANNEL_WX                 => '微信',
        self::PAY_CHANNEL_CASH               => '现金',
        self::PAY_CHANNEL_FY                 => '富友',
        self::PAY_CHANNEL_MYBANK_CONFIG      => '网商分账',
        self::PAY_CHANNEL_MYBANK_PAY         => '网商支付',
        self::PAY_CHANNEL_POST_SALE          => '售后支付',
        self::PAY_CHANNEL_MYBANK_CASHIER_PAY => '网商小程序支付',
    ];


    /**
     * 用户侧 付款渠道, 与 PAY_CHANNEL_TEXT 不相同
     */
    const PAY_CHANNEL_USER_SIDE = [
        self::PAY_CHANNEL_ALI,
        self::PAY_CHANNEL_WX,
        self::PAY_CHANNEL_CASH,
        self::PAY_CHANNEL_FY,
    ];

    const PAY_CHANNEL_CLASS = [
        self::PAY_CHANNEL_ALI                => AliPayEnum::class,
        self::PAY_CHANNEL_WX                 => WxPayEnum::class,
        self::PAY_CHANNEL_CASH               => CashPayEnum::class,
        self::PAY_CHANNEL_MYBANK_PAY         => MyBankPayEnum::class,
        self::PAY_CHANNEL_MYBANK_CASHIER_PAY => MyBankCashierPayEnum::class,
    ];

    // 底层支付渠道
    const PAY_CHANNEL_CHILD_NULL      = 0;
    const PAY_CHANNEL_CHILD_ALI       = 1; // 支付宝
    const PAY_CHANNEL_CHILD_WX        = 2; // 微信
    const PAY_CHANNEL_CHILD_UNION_PAY = 3; // 银联,云闪付

    const PAY_CHANNEL_CHILD_TEXT = [
        self::PAY_CHANNEL_CHILD_ALI       => '支付宝',
        self::PAY_CHANNEL_CHILD_WX        => '微信',
        self::PAY_CHANNEL_CHILD_UNION_PAY => '银联云闪付',
    ];

    // 支付方式
    const PAY_ACTION_NULL   = 0; // 默认
    const PAY_ACTION_SCAN   = 1; // 用户主扫（扫码支付）
    const PAY_ACTION_QRCODE = 2; // 用户被扫（二维码支付）
    const PAY_ACTION_MINI   = 3; // 小程序支付
    const PAY_ACTION_CASH   = 4; // 现金
    const PAY_ACTION_APP    = 5; // app支付
    const PAY_ACTION_WAP    = 6; // H5支付
    const PAY_ACTION_NATIVE = 7; // native支付

    const PAY_ACTIONS = [
        self::PAY_ACTION_NULL   => '默认',
        self::PAY_ACTION_SCAN   => '用户主扫',
        self::PAY_ACTION_QRCODE => '用户被扫',
        self::PAY_ACTION_MINI   => '小程序',
        self::PAY_ACTION_CASH   => '现金',
        self::PAY_ACTION_APP    => 'APP支付',
        self::PAY_ACTION_WAP    => 'H5支付',
        self::PAY_ACTION_NATIVE => 'native支付',
    ];

    // 支付状态
    const PAY_STATUS_0 = 0; // 未支付
    const PAY_STATUS_1 = 1; // 已支付

    // 系统来源
    const SYS_TYPE_RMS_MEAL            = 1; // RMS餐饮
    const SYS_TYPE_ROS                 = 2; // 商家app订货
    const SYS_TYPE_RMS_COUPON_PACKAGE  = 3; // RMS券包
    const SYS_TYPE_OPEN_SHOP_LEAGUE    = 4; // 招商加盟费
    const SYS_TYPE_OPEN_SHOP_DESIGN    = 5; // 设计费
    const SYS_TYPE_OPEN_SHOP_EQUIPMENT = 6; // 设备费
    const SYS_TYPE_OPEN_SHOP_DECORATE  = 7; // 软装费
    const SYS_TYPE_ANNUAL_FEE          = 8; // 数字化年管理费
    const SYS_TYPE_AGAINST             = 9; // 违约函
    const SYS_TYPE_WORK_CLOTHES        = 10; // 工服
    const SYS_TYPE_PSI_TENDER          = 11; // 供应链招标

    const SYS_TYPE_TEXT = [
        self::SYS_TYPE_RMS_MEAL            => 'RMS餐饮',
        self::SYS_TYPE_ROS                 => '商家app订货',
        self::SYS_TYPE_RMS_COUPON_PACKAGE  => 'RMS券包',
        self::SYS_TYPE_OPEN_SHOP_LEAGUE    => '招商加盟费',
        self::SYS_TYPE_OPEN_SHOP_DESIGN    => '招商加盟设计费',
        self::SYS_TYPE_OPEN_SHOP_EQUIPMENT => '招商加盟设备费',
        self::SYS_TYPE_OPEN_SHOP_DECORATE  => '招商加盟软装费',
        self::SYS_TYPE_ANNUAL_FEE          => '数字化年管理费',
        self::SYS_TYPE_AGAINST             => '违约函',
        self::SYS_TYPE_WORK_CLOTHES        => '工服',
        self::SYS_TYPE_PSI_TENDER          => '供应链招标',
    ];

    // 商家APP需要统计的支付类型，堂食相关类型
    const SYS_TYPE_PAY_IN_APP = [
        self::SYS_TYPE_RMS_MEAL,
        self::SYS_TYPE_RMS_COUPON_PACKAGE,
    ];

    const OPERATE_TYPE_PAY    = 1; // 支付
    const OPERATE_TYPE_REFUND = 2; // 退款


    const PAID_IN_ALL = 2; // 所有
    const PAID_IN_YES = 1; // 计入实收
    const PAID_IN_NO  = 0; // 计入优惠

    public function shop()
    {
        return $this->belongsTo(Shop::class, 'shopId', 'id');
    }

    // 对应多个退款记录
    public function refundStreamRel()
    {
        return $this->hasMany(RefundStreamModel::class, 'payNo', 'payNo')
                    ->where('refundStatus', '=', RefundStreamModel::REFUND_STATUS_OK);
    }

    public function getPayChannelNameAttribute()
    {
        return static::PAY_CHANNEL_TEXT[$this->payChannel] ?? '';
    }

    public function getPayChannelChildTextAttribute()
    {
        return self::PAY_CHANNEL_CHILD_TEXT[$this->payChannelChild] ?? '';
    }

    /**
     * 获取支付渠道当前费率
     * @param int $payChannel
     * @return float
     */
    public static function payChannelRate(int $payChannel): float
    {
        $className = static::PAY_CHANNEL_CLASS[$payChannel] ?? null;
        if (class_exists($className)) {
            if (defined("$className::PAYMENT_RATE")) {
                return $className::PAYMENT_RATE;
            }
        }
        return 0;
    }

    /**
     * 支付先入库
     * @param string $payNo
     * @param int $shopId
     * @param int $orderId
     * @param int $payChannel
     * @param int $payChannelChild
     * @param int $payAction
     * @param int $payPrice
     * @param int $fee
     * @param $feeRate
     * @param int $sysType
     * @param string $callbackUrl
     * @param int $isPaidIn
     * @return Builder|\Illuminate\Database\Eloquent\Model
     * @throws PayException
     */
    public static function addData(
        string $payNo,
        int $shopId,
        int $orderId,
        int $payChannel,
        int $payChannelChild,
        int $payAction,
        int $payPrice,
        int $fee,
        $feeRate,
        int $sysType = self::SYS_TYPE_RMS_MEAL,
        string $callbackUrl = '',
        int $isPaidIn = 1,
        string $subject = '',
        string $bizId = '',
        int $userId = 0,
        string $thirdPayNo = '',
        array $extra = [],
        int $company = 0
    ) {
        try {

            // 安全保护，强制重写PayChannelChild
            if ($payChannel == PayStreamModel::PAY_CHANNEL_ALI) {
                $payChannelChild = PayStreamModel::PAY_CHANNEL_CHILD_ALI;
            } elseif ($payChannel == PayStreamModel::PAY_CHANNEL_WX) {
                $payChannelChild = PayStreamModel::PAY_CHANNEL_CHILD_WX;
            }

            try {
                $payConfig      = (new PayConfigService($payChannel, $sysType, $shopId, $company))->getPayConfig();
                $company        = $payConfig['company'];
                $companyAccount = $payConfig['companyAccount'];
            } catch (\Exception $e) {
                $company        = 0;
                $companyAccount = '';
            }

            if (!$bizId) {
                $bizId = (string)$orderId;
            }

            $addParam = [
                'payNo'           => $payNo,
                'shopId'          => $shopId,
                'orderId'         => $orderId,
                'payChannel'      => $payChannel,
                'payChannelChild' => $payChannelChild,
                'payAction'       => $payAction,
                'payStatus'       => self::PAY_STATUS_0,
                'payPrice'        => $payPrice,
                'fee'             => $fee,
                'feeRate'         => $feeRate,
                'sysType'         => $sysType,
                'callbackUrl'     => $callbackUrl,
                'isPaidIn'        => $isPaidIn,
                'subject'         => $subject,
                'company'         => $company,
                'companyAccount'  => $companyAccount,
                'bizId'           => $bizId,
                'createdByUid'    => $userId,
                'thirdPayNo'      => $thirdPayNo,
                'extra'           => $extra ? json_encode($extra, JSON_UNESCAPED_UNICODE) : null,
            ];

            PayStreamMappingModel::addData($addParam);

            return self::suffix($shopId, $sysType)->create($addParam);

        } catch (\Exception $e) {
            throw new PayException(PayCodeEnum::ERR_PAY_DB_ADD, '', $e);
        }
    }

    /**
     * 更新三方支付流水
     * @param string $payNo
     * @param string $thirdPayNo
     * @return int
     */
    public static function saveThirdPaySerialNo(string $payNo, string $thirdPayNo, int $shopId, int $sysType): int
    {
        return self::suffix($shopId, $sysType)->where('payNo', $payNo)->update(['thirdPayNo' => $thirdPayNo]);
    }

    /**
     * 支付成功
     * @param string $payNo
     * @param int $paySuccessTime
     * @param string $thirdPayNo
     * @return bool
     */
    public static function paySuccess(
        string $payNo,
        int $paySuccessTime = 0,
        string $thirdPayNo = ''
    ): bool {
        try {
            $query = self::getDataByPayNo($payNo);
            if (!$query) {
                return false;
            }

            $oriPayStatus = $query->payStatus;

            $query->payStatus = self::PAY_STATUS_1;

            if (!$query->paySuccessTime) {

                if ($paySuccessTime == 0) {
                    $paySuccessTime = time();
                }

                $query->paySuccessTime = $paySuccessTime;
            }

            if (!$query->thirdPayNo && $thirdPayNo) {
                $query->thirdPayNo = $thirdPayNo;
            }

            $query->save();

            return $oriPayStatus == self::PAY_STATUS_0;

        } catch (\Exception $e) {
            throw new PayException(PayCodeEnum::ERR_PAY_DB_EDIT, '', $e);
        }
    }

    public static function getDataByPayNo(string $payNo)
    {
        $mappings = PayStreamMappingModel::getData($payNo);
        if (!$mappings) {
            return self::suffix(null)->where('payNo', $payNo)->first();
        }

        $shopId  = $mappings->shopId;
        $sysType = $mappings->sysType;

        return self::suffix($shopId, $sysType)->where('payNo', $payNo)->first();
    }

    /**
     * 通过orderId查询所有的支付和退款情况
     * 一笔订单会对应多笔支付
     * @param int $orderId
     */
    public static function getPayRefundsByOrderId(
        int $orderId,
        string $payNo = '',
        array $payChannel = [],
        $isPaidIn = PayStreamModel::PAID_IN_ALL,
        int $shopId = 0
    ) {
        $mappings = PayStreamMappingModel::getDataByBizId($orderId, $payNo);
        $shopId   = $mappings['shopId'];
        $sysType  = $mappings['sysType'];

        return PayStreamModel::suffix($shopId, $sysType)
                             ->with('refundStreamRel')
                             ->where('orderId', $orderId)
                             ->when(!empty($payNo), function ($query) use ($payNo) {
                                 $query->where('payNo', $payNo);
                             })
                             ->when(!empty($payChannel), function ($query) use ($payChannel) {
                                 $query->whereIn('payChannel', $payChannel);
                             })
                             ->when($isPaidIn != PayStreamModel::PAID_IN_ALL, function ($query) use ($isPaidIn) {
                                 $query->where('isPaidIn', $isPaidIn);
                             })
                             ->when($shopId, function ($query) use ($shopId) {
                                 $query->where('shopId', $shopId);
                             })
                             ->where('payStatus', self::PAY_STATUS_1)
                             ->orderBy('createdAt')
                             ->get();
    }

    /**
     * 通过orderId查询所有的支付和退款情况
     * 一笔订单会对应多笔支付
     * @param int $orderId
     * @return Builder[]|\Illuminate\Database\Eloquent\Collection
     */

    private static $getOrderPayStreamListByIdReturn;

    public static function getOrderPayStreamListById(
        string $orderId,
        string $payNo = '',
        array $payChannel = [],
        $isPaidIn = PayStreamModel::PAID_IN_ALL
    ) {
        if (empty(self::$getOrderPayStreamListByIdReturn)) {
            $mappings = PayStreamMappingModel::getDataByBizId($orderId, $payNo);
            $shopId   = $mappings['shopId'];
            $sysType  = $mappings['sysType'];

            self::$getOrderPayStreamListByIdReturn =
                PayStreamModel::suffix($shopId, $sysType)
                              ->with('refundStreamRel')
                              ->where('orderId', (int)$orderId)
                              ->when(!empty($payNo), function ($query) use ($payNo) {
                                  $query->where('payNo', $payNo);
                              })
                              ->when(!empty($payChannel), function ($query) use ($payChannel) {
                                  $query->whereIn('payChannel', $payChannel);
                              })
                              ->when($isPaidIn != PayStreamModel::PAID_IN_ALL, function ($query) use ($isPaidIn) {
                                  $query->where('isPaidIn', $isPaidIn);
                              })
                              ->where('payStatus', self::PAY_STATUS_1)
                              ->orderBy('createdAt')
                              ->get()
                              ->each(function ($item) {
                                  $item->validPayPrice = $item->payPrice;
                                  $item->refundStreamRel->each(function ($one) use ($item) {
                                      $item->validPayPrice -= $one->refundPrice;
                                  });
                              });
        }

        return self::$getOrderPayStreamListByIdReturn;
    }

    /**
     * 通过payNo获取子支付渠道
     * @param string $payNo
     * @return int|mixed
     */
    public static function getPayChannelChildByPayNo(string $payNo)
    {
        $mappings = PayStreamMappingModel::getData($payNo);
        if (!$mappings) {
            return 0;
        }

        $shopId  = $mappings->shopId;
        $sysType = $mappings->sysType;

        return self::suffix($shopId, $sysType)->where('payNo', $payNo)->value('payChannelChild') ?? 0;
    }

    /**
     * 结算金额[虚拟属性]
     * 收款 - 手续费
     * @return int 单位：人民币分
     */
    public function getSettlementPriceAttribute(): int
    {
        return $this->payPrice - $this->fee;
    }

    public function getPayChannelSettlementPeriodAttribute()
    {
        $className = static::PAY_CHANNEL_CLASS[$this->payChannel] ?? null;
        if (class_exists($className)) {
            if (defined("$className::PAYMENT_SETTLEMENT_PERIOD")) {
                return $className::PAYMENT_SETTLEMENT_PERIOD;
            }
        }

        // CustomPay
        if ((int)$this->payChannel >= self::PAY_CHANNEL_CUSTOM_PAYMENT) {
            return CustomPayEnum::PAYMENT_SETTLEMENT_PERIOD;
        }

        return '';
    }

    protected static $shopPayments = null;

    public function getPayChannelMerchantIdAttribute()
    {
        if ($this->payChannel == static::PAY_CHANNEL_FY) {
            if (null === static::$shopPayments) {
                static::$shopPayments = [];
                ShopPaymentModel::query()
                                ->where('payChannel', static::PAY_CHANNEL_FY)
                                ->orderBy('id', 'asc')
                                ->chunk(100, function ($models) {
                                    foreach ($models as $model) {
                                        $arr = json_decode($model->payConfig, true);
                                        if (isset($arr['mch_id']) && strlen($arr['mch_id'])) {
                                            static::$shopPayments[$model->shopId] = $arr['mch_id'];
                                        }
                                    }
                                });
            }
            if (isset(static::$shopPayments[$this->shopId])) {
                return static::$shopPayments[$this->shopId];
            }
        }
        return '';
    }

    public function getPayActionTextAttribute()
    {
        return static::PAY_ACTIONS[$this->payAction] ?? '';
    }

    /**
     * 废弃-禁用!!!!
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function streamShopRel()
    {
        return $this->hasOne(ShopPaymentModel::class, 'shopId', 'shopId')->where('payChannel', PayStreamModel::PAY_CHANNEL_FY);
    }

    public function streamCustomPaymentRel()
    {
        return $this->hasOne(CustomPaymentModel::class, 'id', 'payChannel');
    }

    public function getPayStreamListByIds($ids)
    {
        foreach ($ids as &$id) {
            $id = (string)$id;
        }

        // 从业务看只有一家店
        $mappings = PayStreamMappingModel::query()->whereIn('bizId', $ids)->first();
        if (!$mappings) {
            return collect();
        }
        $shopId  = $mappings->shopId;
        $sysType = $mappings->sysType;

        foreach ($ids as &$id) {
            $id = (int)$id;
        }

        $engine = (new SeparateService($mappings->createdAt, $mappings->createdAt))->check();
        if ($engine == SeparateService::DB) {
            $model = PayStreamModel::suffix($shopId, $sysType);
        } else {
            $model = PayStreamCHModel::query()->where('shopId', $shopId);
        }

        return $model
                             ->with([
                                 'streamCustomPaymentRel' => function ($query) {
                                     return $query->select(['id', 'name']);
                                 }
                             ])
                             ->where('payStatus', CodeEnum::ORDER_PAY_STATUS_PAYED)
                             ->whereIn('orderId', $ids)
                             ->get();
    }
}
